package rs.udd.web.controller;

import static rs.udd.service.util.SecurityUtil.ADVERTISER_OR_ADMIN_ROLE;
import static rs.udd.service.util.SecurityUtil.ADVERTISER_ROLE_ONLY;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import rs.udd.constants.GeneralConstants;
import rs.udd.constants.UddMessages;
import rs.udd.exceptions.UddException;
import rs.udd.exceptions.UserAuthenticationCheckException;
import rs.udd.exceptions.password.NewPasswordMismatchException;
import rs.udd.exceptions.password.PasswordMismatchException;
import rs.udd.model.Advert;
import rs.udd.model.Advertiser;
import rs.udd.service.AdvertService;
import rs.udd.service.AdvertiserService;
import rs.udd.service.CategoryService;
import rs.udd.service.LinkService;
import rs.udd.service.MailService;
import rs.udd.service.dto.advert.AdvertDto;
import rs.udd.service.dto.advert.DeleteAdvertDto;
import rs.udd.service.dto.advertiser.AdvertiserEditDto;
import rs.udd.service.dto.advertiser.AdvertiserRegisterDto;
import rs.udd.service.dto.advertiser.AdvertiserViewDto;
import rs.udd.service.dto.user.UserPasswordEditDto;
import rs.udd.service.mappings.AdvertiserMapping;
import rs.udd.service.util.FileUtil;
import rs.udd.service.util.FileUtil.FileTypes;
import rs.udd.service.util.SecurityUtil;
import rs.udd.web.binding.DateBinder;

@Controller
@RequestMapping({"/advertisers", "/advertisers/"})
public class AdvertiserController {

	private static final Logger LOGGER = LogManager.getLogger(AdvertiserController.class);

	@Autowired
	private AdvertiserService advertiserService;

	@Autowired
	private AdvertService advertService;

	@Autowired
	private MailService mailService;

	@Autowired
	private LinkService linkService;

	@Autowired
	private CategoryService categoryService;
	
	@Autowired
	private DateBinder dateBinder;
	
	@InitBinder
	public void initBind(WebDataBinder binder) {
		binder.registerCustomEditor(Date.class, dateBinder);
	}
	
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String handleAddGet(Model model) {
		model.addAttribute("newAdvertiser", new AdvertiserRegisterDto());
		return "advertiser/registerAdvertiser";
	}

	@RequestMapping(value = "/register", method = RequestMethod.POST)
	public String handleAddPost(@Valid @ModelAttribute("newAdvertiser") AdvertiserRegisterDto advertiserDto, BindingResult result, Model model) {

		if (result.hasErrors()) {
			LOGGER.error("Binding error: " + result);
			return "advertiser/registerAdvertiser";
		}

		try {
			LOGGER.info("Registering advertiser...");
			String message = advertiserService.register(advertiserDto);
			model.addAttribute("infoMessage", message);
			
			// when posting a successful form to controller, always do redirect after. google post-get-redirect for a reason.
			return "redirect:/home";

		} catch (UddException e) {
			LOGGER.error("Error while persisting advertiser.", e);
			return "advertiser/registerAdvertiser";
		}

	}
	

	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/uploadAdvert", method = RequestMethod.POST)
	public String uploadFileHandler(HttpSession session,
			@Valid @ModelAttribute("advert") AdvertDto advertDto,
			BindingResult result, Model model) throws UddException {
		
		if (result.hasErrors()) {
			LOGGER.error("Validation of AdvertDto failed: " + result);
			return "/advertiser/uploadAdvert";
		}
		
		boolean validAbstractText = checkAbstractText(advertDto.getAbstractText());
		if (!validAbstractText) {
			LOGGER.error("Abstract text contains more than 10 words");
			result.rejectValue("abstractText", "", "Abstract text contains more than 10 words");
			return "/advertiser/uploadAdvert";
		}
		
		try {
			Advertiser advertiser = advertiserService.getLoggedAdvertiser();
			String fileName = FileUtil.getFolderPathForAdvert(
					advertiser.getUsername(), FileTypes.ADVERT) + advertDto.getFile().getOriginalFilename();
			
			fileName = FileUtil.generateFileName(fileName);
			File convFile = new File(fileName);
			advertDto.getFile().transferTo(convFile);

			advertDto = advertiserService.uploadAdvert(advertDto, convFile);

			if (advertDto.isEmpty()) {
				session.setAttribute("uploadedFile", convFile);
				LOGGER.warn("Metadata missing...");
				return "advertiser/addMetadata";
			} else {
				LOGGER.info("File uploaded successfully.");
				return "/home";
			}

		} catch (IllegalStateException e1) {
			e1.printStackTrace();
		} catch (UddException e) {
			throw new UddException(
					"Something went wrong during the file upload", e);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null; // privremeni fix, nemoj da mi pizdis :) N: oke oke oke oke :)
	}
	
	private boolean checkAbstractText(String abstractText) {
		String[] words = abstractText.split(" ");
		if (words.length <= 10)
			return true;
		
		return false;
	}

	@RequestMapping(value = "/addMetadata", method = RequestMethod.POST)
	public String addMissingMetadata(HttpSession session,
			@Valid @ModelAttribute("advert") AdvertDto advertDto,
			BindingResult result, Model model) throws UddException {
		
		if (result.hasErrors()) {
		}

		File file = (File) session.getAttribute("uploadedFile");

		// advertDto.setFile(file);

		boolean isOk = advertiserService.uploadAdvertAgain(file, advertDto);

		if (isOk) {
			System.out.println("LUDILO MASINA !!!!!");
			return "redirect:/home";
		} else {
			System.out.println("OPET JE NESTO PUKLO !!!!!");
			return "redirect:/500";
		}
	}

	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/prepareUploadAdvert", method = RequestMethod.GET)
	public String prepareUploadAdvert(Model model) {
		AdvertDto advertDto = new AdvertDto();
		model.addAttribute("advert", advertDto);
		return "/advertiser/uploadAdvert";
	}

	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/prepareAdvert", method = RequestMethod.GET)
	public String prepareDownloadAdvert(Model model) {

		Advertiser advertiser = advertiserService.getLoggedAdvertiser();

		// Advertiser advertiser = SecurityUtil.getLoggedUser(Advertiser.class);
		//
		// advertiser.getAdverts().size();

		model.addAttribute("adverts", advertiser.getAdverts());

		return "/advertiser/myAdverts";
	}

	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/downloadAdvert/{id}", method = RequestMethod.GET)
	public String downloadAdvert(@PathVariable Long id, Model model,
			HttpServletResponse resp) throws UddException {

		try {
			// get your file as InputStream
			Advert advert = advertService.findOne(id);
			InputStream is = new FileInputStream(new File(advert.getFilePath()));

			String filename = advert.getTitle() + ".pdf";
			resp.setHeader("Content-Disposition", "attachment;filename=\""
					+ filename + "\"");

			IOUtils.copy(is, resp.getOutputStream());

			resp.flushBuffer();

		} catch (IOException ex) {
			throw new RuntimeException("IOError writing file to output stream", ex);
		}
		return null;
	}

	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/edit", method = RequestMethod.GET)
	public String handleEditGet(HttpSession session, Model model,
			Authentication authentication) throws UddException {

		LOGGER.info("Request to edit advertiser...");
		
		Advertiser advertiser = SecurityUtil.getLoggedUser(Advertiser.class);
		advertiser = advertiserService.findOne(advertiser.getId());

		AdvertiserViewDto editAdvertiserDto = AdvertiserMapping.viewToEdit(advertiser);
		model.addAttribute("editedAdvertiser", editAdvertiserDto);
		
		String previousMail = advertiser.getEmail();
		session.setAttribute("previousEmail", previousMail);

		return "advertiser/editAdvertiser";
	}
	
	@PreAuthorize(ADVERTISER_OR_ADMIN_ROLE)
	@RequestMapping(value = "/view/{id}", method = RequestMethod.GET)
	public String viewAdvertiser(@PathVariable Long id, HttpSession session, Model model,
			Authentication authentication) throws UddException {
		
		Advertiser advertiser = advertiserService.findByIdForView(id);

		AdvertiserViewDto viewAdvertiserDto = AdvertiserMapping.view(advertiser);
		model.addAttribute("viewedAdvertiser", viewAdvertiserDto);

		model.addAttribute("adverts", advertiser.getAdverts());
		model.addAttribute("deleteAdvertDto", new DeleteAdvertDto());
		return "advertiser/viewAdvertiser";
	}
	
	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/edit", method = RequestMethod.POST)
	public String handleEditPost(@Valid @ModelAttribute("editedAdvertiser") AdvertiserEditDto dto, BindingResult result, Model model, HttpSession session,
			Authentication authentication) throws UnsupportedEncodingException, UddException {

		Advertiser advertiser = SecurityUtil.getLoggedUser(Advertiser.class);
		advertiser = advertiserService.findOne(advertiser.getId());
		
		if (result.hasErrors()) {
			LOGGER.error("Binding error," + result);
			return "advertiser/editAdvertiser";
		}
		
		String previousEmail = (String) session.getAttribute("previousEmail");
		
		advertiserService.edit(dto);
		
		String infoMessage = UddMessages.INFO_ADVERTISER_SUCCESSFULLY_EDITED;
		
		if (!dto.getEmail().equals(previousEmail)) {
			String message = UddMessages.MESSAGE_EMAIL_GREETING.replace("[username]", advertiser.getUsername()) + "\n\n" 
					+ UddMessages.MESSAGE_EMAIL_CHANGE_TEXT + "\n\n" + UddMessages.MESSAGE_EMAIL_LINK_DURATION + "\n\n\n" + UddMessages.MESSAGE_EMAIL_VALEDICTION;		
			
			String link = GeneralConstants.APP_LINK + "link/confirmation?random=" + linkService.generateAndSaveAdvertiserLink(advertiser, dto.getEmail());
			
			message = message.replace("[link]", link);
			mailService.sendMail(dto.getEmail(), UddMessages.MESSAGE_EMAIL_CHANGE_SUBJECT, message);
			infoMessage = UddMessages.INFO_ADVERTISER_SUCCESSFULLY_EDITED_EMAIL;
		}

		LOGGER.info("Editing advertiser...");
		
		model.addAttribute("infoMessage", infoMessage);
		
		return "home";
	}


	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/edit-password", method = RequestMethod.GET)
	public String handleEditPasswordPost(Model model) throws UddException {

		Advertiser advertiser = SecurityUtil.getLoggedUser(Advertiser.class);
		advertiser = advertiserService.findOne(advertiser.getId());

		UserPasswordEditDto editPasswordDto = AdvertiserMapping.editPassword(advertiser);
		model.addAttribute("editPasswordDto", editPasswordDto);
		
		return "advertiser/editPassword";
	}

	
	
	@PreAuthorize(ADVERTISER_ROLE_ONLY)
	@RequestMapping(value = "/edit-password", method = RequestMethod.POST)
	public String handleEditPasswordPost(
			@Valid @ModelAttribute("editPasswordDto") UserPasswordEditDto dto,
			BindingResult result, Model model, Authentication authentication) throws UddException {
		
		Advertiser advertiser = SecurityUtil.getLoggedUser(Advertiser.class);
		advertiser = advertiserService.findOne(advertiser.getId());
		
		if (result.hasErrors()) {
			LOGGER.error("Binding error: " + result);
			return "advertiser/editPassword";
		}

		try {
			LOGGER.info("Changing advertiser password...");

			boolean passwordChanged = advertiserService.editPassword(dto);
			if (passwordChanged) {
				LOGGER.info("Advertiser's password edited successfully.");
				// when posting a successful form to controller, always do redirect after. google post-get-redirect for a reason.
				return "redirect:/home";
			} else {
				result.reject("oldPassword", "Old password not valid.");
				return "advertiser/editPassword";
			}

		} catch (UserAuthenticationCheckException e) {
			LOGGER.error("Error while requesting edit advertiser.", e);
			model.addAttribute("message",
					"Requested editing for advertiser that is not currently logged in.");
			return "error/error";
		} catch (PasswordMismatchException e) {

			LOGGER.error(e.getMessage());

			if (e instanceof NewPasswordMismatchException) {
				result.reject("newPassword", e.getMessage());
			}

			return "advertiser/editAdvertiser";

		} catch (UddException e) {
			LOGGER.error(
					"Error while requesting edit password for advertiser.", e);
			model.addAttribute("message", e.getMessage());
			return "error/error";
		}
	}
}
